package demo8

import (
	"errors"
	"fmt"
	"time"
)

/*
	服务器开发中会使用RPC（Remote Procedure Call，远程过程调用）简化进程间通信的过程。
	RPC 能有效地封装通信过程，让远程的数据收发通信过程看起来就像本地的函数调用一样。
	本例中，使用通道代替 Socket 实现 RPC 的过程。客户端与服务器运行在同一个进程，服务器和客户端在两个 goroutine 中运行。
*/

// RPCClient 模拟RPC客户端的请求和接收消息封装
func RPCClient(ch chan string, req string) (string, error) {
	// 向服务器发送请求
	ch <- req

	/*
		select 等待服务器返回
		1. 使用 select 开始做多路复用, 注意，select 虽然在写法上和 switch 一样，都可以拥有 case 和 default。
		2. 但是 select 关键字后面不接任何语句，而是将要复用的多个通道语句写在每一个 case 上
		3. select 中， 2 个 case 的通道操作会同时打开，
			如果第一个 case chan 先返回，则表示接收服务器数据;
			如果第二个 case chan 先返回, 则表示超时错误
	*/
	select {
	// 接受服务器返回数据
	case ack := <-ch:
		return ack, nil
	// 在超时时，返回超时错误。
	// 使用了 time 包提供的函数 After()，从字面意思看就是多少时间之后，其参数是 time 包的一个常量，
	//	time.Second 表示 1 秒。time.After 返回一个通道，这个通道在指定时间后，通过通道返回当前时间
	case <-time.After(time.Second):
		return "", errors.New("Time out")
	}
}

// RPCServer 模拟RPC服务器端接收客户端请求和回应
func RPCServer(ch chan string) {
	for {
		// 接受客户端请求
		data := <-ch

		// 打印收到的数据
		fmt.Println("server received:", data)

		// 通过 time.Sleep() 阻塞程序，模拟超时
		time.Sleep(time.Second * 2)

		// 反馈给客户端
		ch <- "reger"
	}
}

// TestDemo811 多路复用—— RPC 模拟远程过程调用
func TestDemo811() {
	// 创建一个无缓冲的 string channel
	ch := make(chan string)

	// 并发执行服务器逻辑
	go RPCServer(ch)

	// 客户端执行请求和接收数据
	recv, err := RPCClient(ch, "hi")

	if err != nil {
		fmt.Println(err)
	} else {
		// 正常接收数据
		fmt.Println("client reveived", recv)
	}
}
